上一篇撰寫了表單元件的測試程式,今天進一步來針對表單內有非同步驗證時,要如何撰寫單元測試程式。
今天的範例程式會使用 LoginPageCompnent
頁面元件,此頁面元件提供使用者進行登入作業。與先前一樣,在測試模組上加入 Angular Material 模組;並建立一 AuthenticationService
的 Spy 服務物件,來替代原來的登入驗證服務。
let authService: jasmine.SpyObj<AuthenticationService>;
beforeEach(async () => {
authService = jasmine.createSpyObj('AuthenticationService', ['isExists']);
authService.isExists.and.returnValue(of(false).pipe(delay(1000)));
await TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
MatCardModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
],
declarations: [LoginPageComponent],
providers: [{ provide: AuthenticationService, useValue: authService }],
}).compileComponents();
fixture = TestBed.createComponent(LoginPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
如上面程式,為了模擬非同步作業,讓 AuthenticationService
的 isExists
方法延遲了 1 秒才回傳結果。
fakeAsync
檢查使用者不存在錯誤訊息針對這種包含著非同步作業的元件,Angular 提供了 fakeAsync
方法,搭配著 tick()
函式的使用,讓我們可以在單元測試程式以線性的方式撰寫,而非使用 Promise.then()
的巢狀語法來增加控制流的複雜度。
import { fakeAsync, tick } from '@angular/core/testing';
it('利用 fakeAsync 方法測試非同步作業', fakeAsync(() => {
const startTime = Date.now();
tick(500);
const endTime = Date.now();
expect(endTime - startTime).toBe(500);
}));
透過 tick()
方法可以讓我們推進測試時的模擬時間,此方法第一個參數用來指定要推進的毫秒數,預設為 0。
it('當帳號不存在時, 應顯示錯誤訊息為 "此帳號不存在"', fakeAsync(() => {
// Arrange
const formFieldElement = fixture.debugElement.query(
By.directive(MatFormField)
);
const inputElement: HTMLInputElement = formFieldElement.query(
By.css('input')
).nativeElement;
// Act
inputElement.value = 'oliver';
inputElement.dispatchEvent(new Event('input'));
inputElement.dispatchEvent(new Event('blur'));
tick(1000);
fixture.detectChanges();
// Assert
const errorElement = formFieldElement.query(By.directive(MatError));
expect(errorElement.nativeElement.textContent.trim()).toBe('此帳號不存在');
});
因此,我們可以如上面程式,在 it
的測試主體中使用了 fakeAsync(() ⇒ {})
,來測試使用者帳號是否存在這種非同步的需求。如一開始所說,此非同步的驗證會延遲了 1 秒才回傳檢果;所以在測試主體內使用了 tick(1000)
,讓測試內的模擬時間推進 1 秒,再去觸發 Angular 的檢測變更以更新頁面。
whenStable
檢查使用者不存在錯誤訊息除了利用 tick()
方法來模組時間的推進,也可以利用 ComponentFixture
內的 whenState
方法來實際等待非同步作業完成。因此,我們可以搭配著 async
方法,透過 whenStable()
方法來在非同步作業完成後,才去觸發 Angular 變更檢測,以及後續測試情境的驗證。
it('當帳號不存在時, 應顯示錯誤訊息為 "此帳號不存在"', async () => {
// Arrange
const formFieldElement = fixture.debugElement.query(
By.directive(MatFormField)
);
const inputElement: HTMLInputElement = formFieldElement.query(
By.css('input')
).nativeElement;
// Act
inputElement.value = 'oliver';
inputElement.dispatchEvent(new Event('input'));
inputElement.dispatchEvent(new Event('blur'));
await fixture.whenStable();
fixture.detectChanges();
// Assert
const errorElement = formFieldElement.query(By.directive(MatError));
expect(errorElement.nativeElement.textContent.trim()).toBe('此帳號不存在');
});
最後就執行 ng test
來確認測試執行的結果。
這一篇利用 fakeAsync
與 tick
來測試有非同步作業的元件,完整的測試程式可以參考 GitHub。接下來,來說明當元件包含了路由的切換,要如何撰寫單元測試。